﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace LinqToDB.Provider
{
    enum SqlNodeType
    {
        Default,
        Table,
        Alias,
        AliasRef,
        Select,
        SelectMany,
        Where,
        Row,
        ColumnRef,
        Group,
        Lambda,

        //a(b)
        Method,
        //a.b
        Member,
        //a
        Value,
        //a && b
        And,
        //a || b
        Or,
        //!a
        Not,
        //a==b
        Equal,
        //a>b
        GreaterThan,
        //a>=b
        GreaterThanOrEqual,
        //a<b
        LessThan,
        //a<=b
        LessThanOrEqual,
        //-a
        Negate,
        //+a
        UnaryPlus,
        //a+b
        Add,
        //a-b
        Subtract,
        //a*b
        Multiply,
        //a/b
        Divide,
        //a%b
        Modulo,
        Convert,
        NotEqual,
    }

    class SqlNode
    {
        public SqlNode(SqlNodeType nodeType, Expression source)
        {
            this.NodeType = nodeType;
            this.Expression = source;
        }
        public SqlNodeType NodeType { get; private set; }
        public Expression Expression { get; private set; }

        public override string ToString()
        {
            return NodeType.ToString();
        }
    }

    class SqlDefault : SqlNode
    {
        public SqlDefault(string description, object value, Expression source)
            : base(SqlNodeType.Default, source)
        {
            this.Command = description;
            this.Value = value;
        }

        public string Command { get; private set; }
        public object Value { get; private set; }
        List<SqlNode> list = new List<SqlNode>();
        public List<SqlNode> SubNodes { get { return list; } }
    }

    #region Source
    //IQueryable
    abstract class SqlSource : SqlNode
    {
        public SqlSource(SqlNodeType nodeType, Expression source)
            : base(nodeType, source)
        { }
    }

    class SqlTable : SqlSource
    {
        public SqlTable(ITable table, Expression source)
            : base(SqlNodeType.Table, source)
        {
            this.Table = table;
        }
        public ITable Table { get; set; }

        public override string ToString()
        {
            return this.Table.TableName;
        }
    }

    class SqlAlias : SqlSource
    {
        public SqlAlias(Expression source)
            : base(SqlNodeType.Alias, source)
        { }
        public SqlSource Source { get; set; }
        public string Alias { get; set; }

        public override string ToString()
        {
            if(Source is SqlTable)
                return this.Source.ToString() + " AS " + this.Alias;
            else
                return "(" + this.Source.ToString() + ") AS " + this.Alias;
        }
    }

    class SqlSelect : SqlSource
    {
        public SqlSelect(Expression source)
            : base(SqlNodeType.Select, source)
        { }
        public SqlSource From { get; set; }
        public SqlRow Row { get; set; }

        public override string ToString()
        {
            return "Select " + Row.ToString() + " From " + this.From.ToString();
        }
    }

    class SqlSelectMany : SqlSource
    {
        public SqlSelectMany(Expression source)
            : base(SqlNodeType.SelectMany, source)
        { }
        public SqlSource From1 { get; set; }
        public SqlSource From2 { get; set; }
        public SqlRow Row { get; set; }

        public override string ToString()
        {
            return "Select " + Row.ToString() + " From " + this.From1.ToString()+ "," + this.From2.ToString();
        }
    }

    class SqlWhere : SqlSource
    {
        public SqlWhere(Expression source)
            : base(SqlNodeType.Where, source)
        { }
        public SqlSource From { get; set; }
        public SqlExpression Condition { get; set; }

        public override string ToString()
        {
            return "Select * from " + this.From.ToString() + " where " + Condition.ToString();
        }
    }

    class SqlGroup : SqlSource
    {
        public SqlGroup(Expression source)
            : base(SqlNodeType.Group, source)
        { }
    }
    
    #endregion
    class SqlRow : SqlExpression
    {
        public SqlRow(Expression source)
            : base(SqlNodeType.Row, source)
        { }

        List<SqlExpression> list = new List<SqlExpression>();
        public List<SqlExpression> Items { get { return list; } }

        public override string ToString()
        {
            StringBuilder str = new StringBuilder();
            foreach (var item in this.Items)
            {
                if(str.Length==0)
                    str.Append(item.ToString());
                else
                    str.Append("," + item.ToString());
            }
            return str.ToString();
        }
    }
    #region Expression
    abstract class SqlExpression : SqlNode
    {
        public SqlExpression(SqlNodeType nodeType, Expression source)
            : base(nodeType, source)
        { }
    }
    
    class SqlLambda : SqlExpression
    {
        public SqlLambda(Expression source)
            : base(SqlNodeType.Lambda, source)
        {
        }
        public SqlNode Body { get; set; }
    }

    abstract class SqlBinary : SqlExpression
    {
        public SqlBinary(SqlExpression left, SqlExpression right, SqlNodeType nodeType, Expression source)
            : base(nodeType, source)
        {
            this.Left = left;
            this.Right = right;
        }
        public SqlExpression Left { get; set; }
        public SqlExpression Right { get; set; }

        public override string ToString()
        {
            return Left.ToString() + " " + NodeType.ToString() + " " + Right.ToString();
        }
    }

    abstract class SqlUnary : SqlExpression
    {
        public SqlUnary(SqlNodeType nodeType, Expression source)
            : base(nodeType, source)
        { }

        public SqlExpression Value { get; set; }

        public override string ToString()
        {
            return NodeType.ToString() + "(" + Value.ToString() + ")";
        }
    }

    class SqlValue : SqlExpression
    {
        public SqlValue(object value, Expression source)
            : base(SqlNodeType.Value, source)
        {
            this.Value = value;
        }
        public object Value { get; set; }

        public override string ToString()
        {
            if (this.Value is string)
                return "'" + Value.ToString() + "'";
            return Value.ToString();
        }
    }

    class SqlMethod : SqlExpression
    {
        public SqlMethod(Expression source)
            : base(SqlNodeType.Method, source)
        { }

        public string Method { get; set; }
        List<SqlExpression> list = new List<SqlExpression>();
        public List<SqlExpression> Arguments { get { return list; } }
        
        public override string ToString()
        {
            StringBuilder str = new StringBuilder();
            foreach (var item in this.Arguments)
            {
                if(str.Length==0)
                    str.Append(item.ToString());
                else
                    str.Append("," + item.ToString());
            }
            return this.Method + "(" + str.ToString() + ")";
        }
    }

    class SqlAliasRef : SqlExpression
    {
        public SqlAliasRef(Expression source)
            : base(SqlNodeType.AliasRef, source)
        { }
        public string Alias { get; set; }
        public override string ToString()
        {
            return Alias;
        }
    }

    class SqlMember : SqlExpression
    {
        public SqlMember(Expression source)
            : base(SqlNodeType.Member, source)
        { }

        public SqlExpression Object { get; set; }
        public string Member { get; set; }

        public override string ToString()
        {
            return Object.ToString() + "." + this.Member;
        }
    }

    #region SqlUnary
    class SqlNegate : SqlUnary
    {
        public SqlNegate(Expression source)
            : base(SqlNodeType.Negate, source)
        { }
    }
    class SqlNot : SqlUnary
    {
        public SqlNot(Expression source)
            : base(SqlNodeType.Not, source)
        { }
    }
    class SqlConvert : SqlUnary
    {
        public SqlConvert(Expression source)
            : base(SqlNodeType.Convert, source)
        { }
    }
    #endregion
    #region SqlBinary
    class SqlAdd : SqlBinary
    {
        public SqlAdd(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Add, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " + " + Right.ToString();
        }
    }
    class SqlSubtract : SqlBinary
    {
        public SqlSubtract(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Subtract, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " - " + Right.ToString();
        }
    }
    class SqlMultiply : SqlBinary
    {
        public SqlMultiply(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Multiply, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " * " + Right.ToString();
        }
    }
    class SqlDivide : SqlBinary
    {
        public SqlDivide(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Divide, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " / " + Right.ToString();
        }
    }
    class SqlModulo : SqlBinary
    {
        public SqlModulo(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Modulo, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " % " + Right.ToString();
        }
    }
    class SqlAnd : SqlBinary
    {
        public SqlAnd(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.And, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " AND " + Right.ToString();
        }
    }
    class SqlOr : SqlBinary
    {
        public SqlOr(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Or, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " OR " + Right.ToString();
        }
    }
    class SqlLessThan : SqlBinary
    {
        public SqlLessThan(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.LessThan, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " < " + Right.ToString();
        }
    }
    class SqlLessThanOrEqual : SqlBinary
    {
        public SqlLessThanOrEqual(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.LessThanOrEqual, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " <= " + Right.ToString();
        }
    }
    class SqlGreaterThan : SqlBinary
    {
        public SqlGreaterThan(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.GreaterThan, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " > " + Right.ToString();
        }
    }
    class SqlGreaterThanOrEqual : SqlBinary
    {
        public SqlGreaterThanOrEqual(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.GreaterThanOrEqual, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " >= " + Right.ToString();
        }
    }
    class SqlEqual : SqlBinary
    {
        public SqlEqual(SqlExpression left, SqlExpression right, Expression source)
            : base(left, right, SqlNodeType.Equal, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " = " + Right.ToString();
        }
    }
    class SqlNotEqual : SqlBinary
    {
        public SqlNotEqual(SqlExpression left, SqlExpression right, Expression source)
            : base(left,right,SqlNodeType.NotEqual, source)
        { }
        public override string ToString()
        {
            return Left.ToString() + " <> " + Right.ToString();
        }
    }
    #endregion

    #endregion
}
